package cc.yaomm.common.basic.util;

import cc.yaomm.common.basic.base.SuperEnum;
import cc.yaomm.common.basic.lang.bo.BaseLoginBO;
import com.alibaba.fastjson.JSON;
import com.google.common.base.Joiner;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;

import java.util.Base64;
import java.util.Date;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

/**
 * <p>@Description : token工具类</p>
 * <p>@Author : QiLin.Xing </p>
 * <p>@Date : 2019/4/23 0023 上午 9:22 </p>
 */
public class TokenUtils {

    /**
     * 过期时间（单位秒）
     */
    private long expireTime;

    /**
     * redis工具类
     */
    private StringRedisTemplate redisTemplate;

    public TokenUtils(long expireTime, StringRedisTemplate redisTemplate) {
        this.expireTime = expireTime;
        this.redisTemplate = redisTemplate;
    }

    /**
     * <p>@Description : 创建token</p>
     * <p>@Author : QiLin.Xing </p>
     * <p>@Date : 2019/4/23 0023 上午 9:59 </p>
     * token格式：token:应用名:ID-值的MD5-时间戳
     * <p>
     * 调用场景：
     * 1.登录token
     *
     * @param uid     唯一ID
     * @param value   登录内容
     * @param appName 应用名
     * @return
     */
    public String createToken(final long uid, final String value, SuperEnum appName, String loginIP) {
        Assert.isTrue(!StringUtils.isEmpty(value), "创建token用户信息不能为空");
        Assert.isTrue(StringUtils.hasText(appName.getName()), "创建token应用名不能为空");

        String token = Joiner.on(":").join("token", appName.getName(), uid);

        //获取该用户的其他token
        Set<Object> keys = redisTemplate.opsForHash().keys(token);
        if (!CollectionUtils.isEmpty(keys)) {
            keys.forEach(key -> {
                String message = "系统检测到您的帐号于【" + ConvertUtils.toString(new Date()) + "】在【" + loginIP + "】登陆，如果不是您的操作，您的帐号密码可能已泄露，请登陆系统或联系管理员修改密码或冻结帐户";
                BaseLoginBO login = new BaseLoginBO();
                login.setMessage(message);
                login.setLoginTime(System.currentTimeMillis());
                redisTemplate.opsForHash().put(token, key, JSON.toJSONString(login));
            });
        }

        //生成新用户token
        String tokenKey = DigestUtils.md5DigestAsHex(value.concat(String.valueOf(System.currentTimeMillis())).getBytes());
        redisTemplate.opsForHash().put(token, tokenKey, value);
        redisTemplate.boundHashOps(token).expire(expireTime, TimeUnit.SECONDS);

        //将token转为base64
        return Base64.getEncoder().encodeToString(Joiner.on("-").join(token, tokenKey).getBytes());
    }

    /**
     * <p>@Description : 获取/验证token，并刷新过期时间</p>
     * <p>@Author : QiLin.Xing </p>
     * <p>@Date : 2019/4/23 0023 上午 10:07 </p>
     * <p>
     * 调用场景：
     * 1.token验证拦截器，验证token是否有效
     * 2.不建议通过此方法获取用户信息，而是通过request获取（在token验证拦截器获取时，同时把用户信息解析后放入request属性中）
     *
     * @param token
     * @return null表示验证失败
     */
    public String getToken(String token) {
        Assert.isTrue(!StringUtils.isEmpty(token), "获取token时参数token不能为空");
        token = new String(Base64.getDecoder().decode(token));

        //根据token获取redis值
        String[] tokenArr = token.split("-");
        Assert.isTrue(tokenArr.length == 2, "解析token时token格式错误");

        Map<Object, Object> entries = redisTemplate.opsForHash().entries(tokenArr[0]);
        if (CollectionUtils.isEmpty(entries)) {
            return null;
        }

        //循环处理并解析token的value
        AtomicReference<String> value = new AtomicReference<>();
        entries.forEach((k, v) -> {
            if (k.equals(tokenArr[1])) {
                value.set(v.toString());
            }
            BaseLoginBO login = JSON.parseObject(v.toString(), BaseLoginBO.class);
            //判断该token是否已经过期，清理掉
            if (Objects.nonNull(login) && StringUtils.hasText(login.getMessage()) &&
                    Objects.nonNull(login.getLoginTime()) &&
                    System.currentTimeMillis() - login.getLoginTime() > 300) {
                redisTemplate.opsForHash().delete(tokenArr[0], k);
            }
        });

        //刷新token的过期时间
        redisTemplate.boundHashOps(tokenArr[0]).expire(expireTime, TimeUnit.SECONDS);
        return value.get();
    }

    /**
     * <p>@Description : 清空登录token</p>
     * <p>@Author : QiLin.Xing </p>
     * <p>@Dte : 2020/8/20  </p>
     * <p>
     * 调用场景：
     * 1.退出登录
     * 2.修改用户密码（或者修改用户信息需要重新登录）
     * 3.登录信息失败，多个人登录时，提出前一个登录者
     *
     * @param token 登录token
     */
    public void clearToken(String token) {
        token = new String(Base64.getDecoder().decode(token));
        //根据token获取redis值
        String[] tokenArr = token.split("-");
        Assert.isTrue(tokenArr.length == 2, "解析token时token格式错误");

        redisTemplate.delete(tokenArr[0]);
    }

    /**
     * 获取token过期时间
     *
     * @return
     */
    public long getExpireTime() {
        return expireTime;
    }
}
